
//=======================================================================================
// Purpose: To learn the detail of how the FAT32 file system functions without the need to
//          actually build any hardware.  User only need a computer running Microsoft 
//          Windows and a SD card reader.  It allows the user to tests out the FAT32 file 
//          system software developed by: CC Dharmani, Chennai (India) www.dharmanitech.com
//          before actually porting it to a microcontroller.  Tracing the code can reveal
//          many compatibility problems between how the official FAT32 documentation and
//          how Microsoft Windows actually implement the FAT32 system.
//
// Recommended software: Microsoft Visual C++ Express (www.microsoft.com/express/)
//                       HxD Hex Editor and Disk Editor (mh-nexus.de/en/hxd/)
//                       The DevCon command-line utility functions (support.microsoft.com/kb/311272)
//
// How to use: Only five files: MAIN.CC (this file), SDCARD.CC, FAT32.CC, FAT32.H, GLOBAL.H
//             Open Microsoft Visual C++ Express and build these files using default built option.
//             Trace the program or use HxD Hex Editor to study the SD card content.
//             Since data is cached in Windows, you need to unplug and plug the USB card reader 
//             for write or delete command to be visible in Windows.  If you want to test the
//             software using a batch file, you need to use the DevCon utility function:
//                 Execute this command to find out the hardware ID of the SD card reader:
//                     C:> devcon find USBSTOR\DISK*
//                 Use the following commands to simulate plug and unplug of the SD card reader:
//                     C:> devcon disable <hardware ID>
//                     C:> devcon enable  <hardware ID>
//
// Prepared by: Henry Yiu (hyiu00@yahoo.com)
//              Date - 2010-12-03
// 
//=======================================================================================

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <iostream>
#include <fstream>
#include <map>
#include <ctype.h>
#include <vector>
#include <cstdint>

#include "fatdefs.h"

#include "GLOBAL.H"
#include "FAT32.H"

/************NOT MY CODE****************************/
// Global variables for file read / write access.
unsigned char  FileData[MAX_FILE_SIZE];
unsigned long  FileSize;
unsigned char   buffer[MAX_SEC_PER_CLS*BYTE_PER_SECTOR];
unsigned long   firstDataSector, rootCluster, totalClusters;
unsigned short  sectorPerCluster, reservedSectorCount;
unsigned long   unusedSectors;
unsigned char   freeClusterCountUpdated;

#pragma warning (disable : 4995)
#pragma warning (disable : 4996)


// Global variables for disk and file name.
char           driveLetter;
unsigned char  fileName[13];
/*****************************************************/

using namespace std;

/******************************** MY CODE **********************************/
struct SongRecord_t
{
	uint8_t filename[11];
	uint8_t songname[30];
};

struct AlbumRecord_t
{
	uint8_t albumname[30];
	uint16_t songs;
	uint16_t songindex;
} ;

struct ArtistRecord_t
{
	uint8_t aristname[30];
	uint16_t albums;
	uint16_t albumindex;
	uint16_t songs;
	uint16_t songindex;
} ;

struct MusicLibHeader_t
{
	uint8_t header[12]; // Equals MUSICLIBv0.1
	uint16_t artists;
	uint16_t albums;
	uint16_t songs;
} ;

/**************************************************************************
* Purpose: Parses a string and shortens it by removing nonprintable chars
*			upto the true end of the string (removes exess spaces, punct,
*			etc)
* Input: String to be handled, and the len of the buffer for string
* Output: No Output
***************************************************************************/
void trim(char * buf, int len);


int main (int argc, char *argv[])
{
	char cmdstr[100];
	
	unsigned long i;
	unsigned long count = 0;
	
	std::map<std::string,int> artists;
	std::map<std::string,int> albums;
	std::map<std::string,int>::iterator it_artist, it_album;

	std::vector<struct SongRecord_t> songRecords;
	std::vector<struct AlbumRecord_t> albumRecords;
	std::vector<struct ArtistRecord_t> artistRecords;

	struct FAT_FILE_t **fpArray;
	struct SongData ** songs; 

	// Prompt for drive letter to open
	printf("Drive to open: ");

	// Get string
	scanf("%s",cmdstr);

	// First character should be drive letter...
	driveLetter = cmdstr[0];

	//Attempt to open drive
	if (!CardOpen())
	{
		printf("Failed to open drive: %c\n",driveLetter);
		return -1;
	}

	// Attempt to setup the fat library
	if (!FAT32Init())
	{
		printf("Failed to initalize Fat32 Driver\n");
		return -1;
	}

	//listfiles();

	// Will be used to write data to file
	ofstream out;

	// Open output stream that will be binary, and overwrite any existing file
	out.open("MUSICLIB.DAT",ios::out|ios::trunc|ios::binary);

	// Ensure stream is good
	if (out.bad())
	{
		cout << "Failed to open musiclib.dat" << endl;
		return -1;
	}

	// Determine how many mp3 files there are in drive
	count = getMp3Count();
	printf("Count: %lu\n",count);

	// Allocate memory for enough fileptrs as there are mp3 files
	fpArray = (struct FAT_FILE_t**) malloc ( count * sizeof(struct FAT_FILE_t*) );

	// Allocate enough memory for songdata struct pointers are there are files
	songs = (struct SongData**)malloc(count * sizeof(SongData*));

	// For each file, get it's fileptr, create memory for songdata, and populate
	// with id3v1 data if available
	for (i = 0; i < count; i++)
	{
		fpArray[i] = getNextMp3File(i);

		songs[i] = (struct SongData*)malloc(count * sizeof(SongData));

		// Ensure valid ptr
		if (fpArray[i] == NULL)
		{	
			// Fail if not
			printf("Error allocating memory for all files\n");

			// Free up any already allocated memory
			for (count = 0; count < i; count++)
			{
				free(fpArray[count]);
				free(songs[count]);
			}
			free(songs);
			free(fpArray);

			return -1;
		}
	}
	
	// For each file, trim it's song data, and ensure it's of valid form
	for (i = 0; i < count; i++)
	{
		getSongData(songs, fpArray, i);
		trim(songs[i]->artist,30);
		trim(songs[i]->title,30);
		trim(songs[i]->album,30);

		// Ensure valid form for all 3 categories
		if (strnlen((char*)songs[i]->artist,30) == 0)
		{
			char tempstr[] = "Unknown Artist";
			memcpy(songs[i]->artist,tempstr,sizeof(tempstr));
		}
		if (strnlen((char*)songs[i]->album,30) == 0)
		{
			char tempstr[] = "Unknown Album";
			memcpy(songs[i]->album,tempstr,sizeof(tempstr));
		}
		// If invalid songname, give it a songname = to it's sfn
		if (strnlen((char*)songs[i]->title,30) == 0)
		{
			memcpy(songs[i]->title,songs[i]->filename,12);
		}

	}

	// Free up memory we no longer need
	for (i = 0; i < count; i++)
		free(fpArray[i]);

	free(fpArray);

	/* For each song fill our maps with new artists/albums
	* Maps prevent duplication of artists, and allow us to track
	* the number of songs associated with each artist and album */
	for (i = 0; i < count; i++)
	{
		// Populate with first songs data
		if(artists.empty())
		{
			artists.insert(pair<string, int> (string(songs[i]->artist),1));
			albums.insert(pair<string, int> (string(songs[i]->album),1));
		}

		else
		{
			it_artist = artists.find((char*)songs[i]->artist); // Search for existing artist
			it_album = albums.find((char*)songs[i]->album); // Search for existing album

			if (it_artist == artists.end()) // The artist does not exist
			{
				artists.insert(pair <string, int> (string(songs[i]->artist),1));
				albums.insert(pair<string, int> (string(songs[i]->album),1));
			}

			else if (it_album == albums.end()) // The artist exists but the album does not
			{
				albums.insert(pair<string,int>(string(songs[i]->album),1));
				it_artist->second++; // Increase the album count
			}
			else // The album and artist exist so just increment the song count
			{
				it_album->second++;
			}
		}
	}

	unsigned long j, k, x;

	AlbumRecord_t tempAlbum;
	SongRecord_t tempRecord;
	ArtistRecord_t tempArtist;

	// For each artist load up album vector/song vector in order
	for(it_artist = artists.begin(), j = 0, k = 0; it_artist != artists.end(); it_artist++)
	{
		// Populate artist record with artist info
		memset(&tempArtist,0,sizeof(struct ArtistRecord_t));
		memcpy(tempArtist.aristname,it_artist->first.c_str(),30);

		// Figure out how many albums there are for this artist
		tempArtist.albums = it_artist->second;
		tempArtist.albumindex = j;

		j+=it_artist->second;

		tempArtist.songindex = k;

		// Create a vector to keep track of songs in order of album by artist
		vector<unsigned int> song_indexes;
		
		map<string,int> albums_loc;

		// For each album 
		for (i = 0; i < count; i++)
		{
			// ensure valid data and that this song's album actually belongs to this artist(dup album names allowed)
			if ((songs[i] != NULL) && (strnlen(songs[i]->artist,30) == it_artist->first.length()))
			{
				
				// Find all songs that belong in a given album
				if (memcmp(songs[i]->artist,it_artist->first.c_str(),it_artist->first.length()+1)==0)
				{
					// Load song into vector
					song_indexes.push_back(i);

					// find the next song in array with matching album
					it_album = albums_loc.find(songs[i]->album);

					// If no album exists for this artist, add it
					if (it_album == albums_loc.end())
					{
						albums_loc.insert(pair<string,int> (songs[i]->album, 1));
					}
					// Otherwise add album's song count
					else
					{
						it_album->second++;
					}
				}
			}
		}

		// For each album gather all songs in order into final song vector, which will be used
		// to write out our song index
		for (it_album = albums_loc.begin(), x = 0; it_album != albums_loc.end(); it_album++)
		{
			// Clear the album rec
			memset(&tempAlbum,0,sizeof(tempAlbum));
			// Load with data
			memcpy(tempAlbum.albumname,it_album->first.c_str(), it_album->first.length()+1);
			tempAlbum.songs = it_album->second;
			tempAlbum.songindex = k;

			// Push album into final album vector (keeps things ordered)
			albumRecords.push_back(tempAlbum);
			 
			// FOr each song in the song index array look for next song belonging to this album
			for( i = 0; i < song_indexes.size(); i++)
			{
				// If this song belongs to this album/artist
				if (songs[song_indexes[i]] != NULL && (strnlen(songs[song_indexes[i]]->album,30) == it_album->first.length()))
				{
					
					if (memcmp(songs[song_indexes[i]]->album,it_album->first.c_str(),it_album->first.length()+1)==0)
					{
						// Clear temp record, pop with data
						memset(&tempRecord,0,sizeof(tempRecord));
						memcpy(tempRecord.filename,songs[song_indexes[i]]->filename,11);
						memcpy(tempRecord.songname,songs[song_indexes[i]]->title,30);

						// Push into final song vector
						songRecords.push_back(tempRecord);

						// Free up the no longer needed data
						free(songs[song_indexes[i]]);
						// Set fileptr to null so we know to skip it
						songs[song_indexes[i]] = NULL;
						x++;
						k++; // move forward
					}
				}
			}			
		}

		// Load artist with total songs we have gathered from all albums
		tempArtist.songs = x;

		// Load the finished artist record into final artist vector which will be used to write
		// our artist index
		artistRecords.push_back(tempArtist);
					
	}
	
	// Create our musiclib header 
	struct MusicLibHeader_t musicheader;

	// Pop with data from final vectors
	musicheader.songs = songRecords.size();
	musicheader.albums = albumRecords.size();
	musicheader.artists = artistRecords.size();

	// Load with our "special" string (ensures it's our library who made this) 
	memcpy(musicheader.header, "MUSICLIBv0.1", 12);
	
	// Write all our data to file
	out.write((char*)&musicheader.header, 12);
	out.write((char*)&musicheader.artists,2);
	out.write((char*)&musicheader.albums,2);
	out.write((char*)&musicheader.songs,2);

	for (i = 0; i < artistRecords.size(); i++)
	{
		out.write((char*)&artistRecords[i].aristname,30);
		out.write((char*)&artistRecords[i].albums,2);
		out.write((char*)&artistRecords[i].albumindex,2);
		out.write((char*)&artistRecords[i].songs,2);
		out.write((char*)&artistRecords[i].songindex,2);
	}
	for (i = 0; i < albumRecords.size(); i++)
	{
		out.write((char*)&albumRecords[i].albumname,30);
		out.write((char*)&albumRecords[i].songs,2);
		out.write((char*)&albumRecords[i].songindex,2);
	}
	for (i = 0; i < songRecords.size(); i++)
	{
		out.write((char*)&songRecords[i].filename,11);
		out.write((char*)&songRecords[i].songname,30);
	}

	// Close the file now that were done
	out.close();

	// Free up the vectors
	artists.clear();
	albums.clear();

	// Free up the songs
	free(songs);

	return 0;
}

/**************************************************************************
* Purpose: Parses a string and shortens it by removing nonprintable chars
*			upto the true end of the string (removes exess spaces, punct,
*			etc)
* Input: String to be handled, and the len of the buffer for string
* Output: No Output
***************************************************************************/
void trim(char * buf, int len)
{
	int i;
	char ch;

	// Do inital check to remove unwanted characters between the end of string and the end of the array 
	for ( i = 0; i < len; i++)
	{
		ch = buf[i];

		// Remove all non-printable characters, replace with a space
		if (!isprint(ch) && !iscntrl(ch) && ch != '\0')
			buf[i] = ' ';

		// iscntrl also includes stuff like '\r','\n' etc, remove these
		if (buf[i] == '\r' || buf[i] == '\n')
			buf[i] = ' ';
	}

	// Find the true end of string (where the last letter or printable (not including spaces) occurs)
	// And indicate it with a '\0' null terminator
	for (i = len - 1; i > 0; i--)
	{
		ch = buf[i];
		if (isprint(ch) || iscntrl(ch))
			break;

		buf[i] = '\0';
	}
}